/**
 * Title: Hyperbolic engine
 * Description: jFamilyTree Engine
 * Version: provider
 * Copyright: (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 * Update: Mon Mar 10 00:00:00 CEST 2008
 */

package jFamilyTree.control;

import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import jFamilyTree.core.Complex;
import jFamilyTree.core.EuclidianLocation;
import jFamilyTree.model.INode;

/**
 * Mouse adapter
 * 
 * @author Bernard Bou
 */
public class MouseAdapter implements MouseListener, MouseMotionListener
{
	// D A T A

	/**
	 * Controller
	 */
	private Controller theController;

	// D R A G A N D D R O P

	/**
	 * Drag mode enum
	 */
	static private enum DragMode
	{
		TRANSLATE, ROTATE
	}

	/**
	 * Drag mode
	 */
	private DragMode theDragMode;

	/**
	 * Whether mouse was dragged
	 */
	private boolean wasDragged = false;

	/**
	 * Whether mouse was moved
	 */
	private boolean wasMoved = false;

	// popup behaviour

	/**
	 * Whether it has popup
	 */
	public boolean hasPopUp = true;

	// translation
	/**
	 * Drag starting point
	 */
	private Complex theDragStart = new Complex();

	/**
	 * Drag end point
	 */
	private Complex theDragEnd = new Complex();

	// selection
	/**
	 * Hot node
	 */
	private INode theHotNode = null;

	// C O N S T R U C T O R

	/**
	 * Constructor
	 * 
	 * @param thisController
	 *        controller
	 */
	public MouseAdapter(Controller thisController)
	{
		theController = thisController;
	}

	// M O U S E L I S T E N E R

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	public void mouseClicked(MouseEvent thisEvent)
	{
		int vx = thisEvent.getX();
		int vy = thisEvent.getY();
		INode thisNode = theController.findNode(vx, vy);
		if (thisNode != null)
		{
			// right click
			if (hasPopUp && (thisEvent.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK)
				theController.handle(Controller.Event.POPUP, new Pair<Object>(new Point(thisEvent.getX(), thisEvent.getY()), thisNode));

			// control down and click
			else if (thisEvent.isControlDown())
				theController.handle(Controller.Event.MOUNT, thisNode);

			// shift down and click
			else if (thisEvent.isShiftDown())
				theController.handle(Controller.Event.LINK, thisNode);

			// other
			else if (!thisEvent.isControlDown() && !thisEvent.isShiftDown())
				theController.handle(Controller.Event.FOCUS, thisNode);
		}
		thisEvent.consume();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	public void mousePressed(MouseEvent thisEvent)
	{
		Complex thisDragStart = theController.viewToUnitCircle(new Point(thisEvent.getX(), thisEvent.getY()));
		theDragMode = thisEvent.isShiftDown() ? DragMode.ROTATE : DragMode.TRANSLATE;
		theDragStart.set(thisDragStart);
		theDragEnd.set(theDragStart);
		thisEvent.consume();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(MouseEvent thisEvent)
	{
		// drag
		if (wasDragged)
		{
			wasDragged = false;
			theController.handle(Controller.Event.LEAVEDRAG, null);
		}
		else
		{
			// selection
			int vx = thisEvent.getX();
			int vy = thisEvent.getY();
			INode thisNode = theController.findNode(vx, vy);
			if (thisNode != null)
				theController.handle(Controller.Event.SELECT, thisNode);
		}
		thisEvent.consume();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	public void mouseEntered(MouseEvent thisEvent)
	{
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	public void mouseExited(MouseEvent thisEvent)
	{
	}

	// M O U S E M O T I O N L I S T E N E R

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	public void mouseMoved(MouseEvent thisEvent)
	{
		int vx = thisEvent.getX();
		int vy = thisEvent.getY();
		INode thisNode = theController.findNode(vx, vy);
		boolean again = theHotNode == thisNode;
		theHotNode = thisNode;
		if (thisNode != null && !again)
			theController.handle(Controller.Event.HOVER, thisNode);
		thisEvent.consume();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
	 */
	public void mouseDragged(MouseEvent thisEvent)
	{
		final double theMaxShiftSpan = .5;

		// avoid wide mouse shift which will lead to cross-circle xlations
		Complex thisDragEnd = theController.viewToUnitCircle(new Point(thisEvent.getX(), thisEvent.getY()));
		if (EuclidianLocation.getDistance(theDragStart, thisDragEnd) > theMaxShiftSpan)
		{
			// keep shift direction but limit span
			thisDragEnd = Complex.makeFromArgAbs(thisDragEnd.sub(theDragStart).arg(), theMaxShiftSpan).add(theDragStart);
		}
		theDragEnd = thisDragEnd;
		wasMoved = true;
		wasDragged = true;
		theController.handle(Controller.Event.DRAG, null);
		thisEvent.consume();
	}

	// D R A G M O V E

	/**
	 * Rotate
	 */
	private synchronized void rotate()
	{
		theController.handle(Controller.Event.ROTATE, new Pair<Complex>(new Complex(theDragStart), new Complex(theDragEnd)));
		theDragStart.set(theDragEnd); // eat
	}

	/**
	 * Move
	 */
	private synchronized void move()
	{
		theController.handle(Controller.Event.MOVE, new Pair<Complex>(theDragStart, theDragEnd));
		theDragStart.set(theDragEnd); // eat
	}

	/**
	 * Drag
	 * 
	 * @return true if successful
	 */
	public boolean drag()
	{
		if (wasMoved)
		{
			// move it now
			switch (theDragMode)
			{
			case TRANSLATE:
				move();
				break;

			case ROTATE:
				rotate();
			}
			wasMoved = false;
			return true;
		}
		return false;
	}

	// H O T N O D E

	/**
	 * Reset hot node
	 */
	public void resetHotNode()
	{
		theHotNode = null;
	}

	/**
	 * Get hot node
	 * 
	 * @return hot node
	 */
	public INode getHotNode()
	{
		return theHotNode;
	}
}
